//------------------------------------------------------------------------------
//SFWK Framework
//Copyright (C) 2011 SQLI

//This program is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, either version 3 of the License, or
//(at your option) any later version.

//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with this program.  If not, see <http://www.gnu.org/licenses/>.
//------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;
using System.Collections;
using System.Diagnostics;

namespace SFWK.Generator
{
    public static class InferAssemblyService
    {
        #region Get Named attributes

        public static string GetCompositClassName(Type type)
        {
            return ((string)GetCustomAttributeProperty(type, "CompositClassName"));
        }
        public static string GetServiceContract(Type type)
        {
            Type t = (Type)GetCustomAttributeProperty(type, "ServiceContract");
            if(t != null)
                return t.Name;
            return "";
        }

        public static string GetGetByParentIDMethodName(Type type)
        {
            return (string)GetCustomAttributeProperty(type, "GetByParentID", false);
        }
        



        public static string GetFKName(PropertyInfo property)
        {
            string res = (string)GetCustomAttributeProperty(property, "FKName");
            return res;
        }

        public static string[] GetPKNames(PropertyInfo property)
        {
            return (string[])GetCustomAttributeProperty(property, "PKNames");
        }

        public static string[] GetPrimaryKeyNames(PropertyInfo prop)
        {
            return (string[])GetCustomAttributeProperty(prop, "PrimaryKeyNames");
        }
        #endregion Get Named properties

        #region Get Objets and assemblies
        public static IEnumerable<Type> GetAssemblyClasses(Assembly assembly)
        {
            foreach (Type type in assembly.GetTypes())
            {
                yield return type;
            }
        }
        public static IEnumerable<Type> GetCompositeDescriptorClasses(string assembly)
        {
            return GetAssemblyClasses(Assembly.LoadFrom(assembly)).Where((Type type) => { return HasAttribute(type, "SFWK.Core.CompositionAttribute.CompositeDescriptor"); });
        }

        public static Type GetContract(string assembly, string interfaceName)
        {
            Assembly ass = Assembly.LoadFrom(assembly);

            return ass.GetType(interfaceName);
        }

        public static PropertyInfo GetCompositeBaseObject(Type type)
        {
            return type.GetProperties().Where(p => HasAttribute(p, "SFWK.Core.CompositionAttribute.CompositeBase")).FirstOrDefault();
        }

        public static IEnumerable<PropertyInfo> GetCompositionObjectProperties(Type type)
        {
            return type.GetProperties().Where(p => HasAttribute(p, "SFWK.Core.CompositionAttribute.CompositionObject"));
        }
        #endregion Get Objets and assemblies

        #region Get attributes & properties
        public static bool HasAttribute(object parent, string attributeFullName)
        {
            object[] attributes = null;
            if (parent is PropertyInfo)
                attributes = ((PropertyInfo)parent).GetCustomAttributes(false);
            else if (parent is Type)
                attributes = ((Type)parent).GetCustomAttributes(false);

            if ((attributes != null) && (attributes.Length > 0))
            {
                foreach (object o in attributes)
                {
                    if (o.GetType().FullName == attributeFullName)
                        return true;
                }
            }
            return false;
        }

        public static object GetCustomAttributeProperty(object parent, string propertyName)
        {
            return GetCustomAttributeProperty(parent, propertyName, true);
        }
        public static object GetCustomAttributeProperty(object parent, string propertyName, bool mandatory)
        {
            object[] attributes = null;

            if (parent is PropertyInfo)
                attributes = ((PropertyInfo)parent).GetCustomAttributes(false);
            else if (parent is Type)
                attributes = ((Type)parent).GetCustomAttributes(false);

            if ((attributes != null) && (attributes.Length > 0))
            {
                foreach (object o in attributes)
                {
                    PropertyInfo pi = o.GetType().GetProperty(propertyName);
                    if (pi != null)
                        return pi.GetValue(o, null);
                }
            }
            if(mandatory)
                throw (new InferAssemblyException(
                Environment.NewLine + "\t\t- Propriétée non trouvé de : " +
                Environment.NewLine + "\t\t- " + ((parent is PropertyInfo) ? ((PropertyInfo)parent).PropertyType.Name : ((Type)parent).Name) + " -> \"" + propertyName + "\""));
            return null;
        }
        #endregion Get attributes

        #region Get Namespaces
        public static List<string> GetCompositionNamespaces(Type type)
        {
            List<string> res = new List<string>();
            res.Add(GetCompositeBaseObject(type).PropertyType.Namespace.Trim());
        
            foreach (PropertyInfo property in GetCompositionObjectProperties(type))
            {
                if(!(property.PropertyType.Name == "List`1"))
                {
                    if (!res.Contains(property.PropertyType.Namespace.Trim()))
                        res.Add(property.PropertyType.Namespace.Trim());
                }
                else
                {
                    if ((property.PropertyType.GetGenericArguments().Count() > 0) &&
                    !res.Contains(property.PropertyType.GetGenericArguments()[0].Namespace.Trim()))
                        res.Add(property.PropertyType.GetGenericArguments()[0].Namespace.Trim());
                }
            }
            return res;
        }

        public static List<string> GetAllCompositionObjectNamespaces(string assembly)
        {
            List<string> res = new List<string>();

            foreach (Type type in GetCompositeDescriptorClasses(assembly))
            {
                if(!res.Contains(GetCompositeBaseObject(type).PropertyType.Namespace.Trim()))
                    res.Add(GetCompositeBaseObject(type).PropertyType.Namespace.Trim());
                foreach (PropertyInfo property in GetCompositionObjectProperties(type))
                {
                    if (!(property.PropertyType.Name == "List`1"))
                    {
                        if (!res.Contains(property.PropertyType.Namespace.Trim()))
                            res.Add(property.PropertyType.Namespace.Trim());
                    }
                    else
                    {
                        if ((property.PropertyType.GetGenericArguments().Count() > 0) &&
                        !res.Contains(property.PropertyType.GetGenericArguments()[0].Namespace.Trim()))
                            res.Add(property.PropertyType.GetGenericArguments()[0].Namespace.Trim());
                    }
                }
            }
            return res;
        }
        #endregion Get Namespaces

        #region Get Composition Primitives & Navigation proerties
        public static List<PropertyInfo> GetPrimitiveProperties(Type type)
        {
            List<PropertyInfo> res = new List<PropertyInfo>();
            foreach (PropertyInfo property in GetCompositionObjectProperties(type))
            {
                if (property.PropertyType.Name != "List`1")
                {
                    if (!res.Contains(property))
                        res.Add(property);
                }
            }
            return res;
        }

        public static List<PropertyInfo> GetNavigationProperties(Type type)
        {
            List<PropertyInfo> res = new List<PropertyInfo>();
            foreach (PropertyInfo property in type.GetProperties())
            {
                if (property.PropertyType.Name == "List`1" && property.PropertyType.GetGenericArguments().Count() > 0)
                    if (!res.Contains(property))
                        res.Add(property);
            }
            return res;
        }
        #endregion

        #region Get Types
        public static string GetFieldType(Type objectType, string fieldName)
        {
            try
            {
                return objectType.GetProperty(fieldName).PropertyType.Name;
            }

            catch
            {
                throw (new InferAssemblyException(
                Environment.NewLine + "\t\t- Champ non trouvé de " + objectType.Name + " : " +
                Environment.NewLine + "\t\t- " + fieldName));
            }
        }


        public static bool IsFieldNullableType(Type objectType, string fieldName)
        {
            return GetFieldType(objectType, fieldName) == "Nullable`1";            
        }

        public static string GetFieldNonNullableType(Type objectType, string fieldName)
        {
            try
            {
                if(IsFieldNullableType(objectType, fieldName))
                    return objectType.GetProperty(fieldName).PropertyType.GetGenericArguments()[0].Name;
            }

            catch
            {
                throw (new InferAssemblyException(
                Environment.NewLine + "\t\t- Champ non trouvé de " + objectType.Name + " : " +
                Environment.NewLine + "\t\t- " + fieldName));
            }
            return GetFieldType(objectType, fieldName);
        }
        #endregion Get Types

        #region Format objects for generation
        public static string GetCompositionObjectsAsParameters(Type compositClass)
        {
            string res = "";
            foreach (PropertyInfo navigationProp in GetNavigationProperties(compositClass))
		    {
			    string navigationObjectName = navigationProp.PropertyType.GetGenericArguments()[0].Name;
                res += "List<" + navigationObjectName + "> " + navigationObjectName.Substring(0, 1).ToLower() + navigationObjectName.Substring(1) + ", ";
		    }

            foreach (PropertyInfo prop in GetPrimitiveProperties(compositClass))
            {
                res += prop.Name + " " + prop.Name.Substring(0, 1).ToLower() + prop.Name.Substring(1) + ", ";
            }
            res = res.TrimEnd(',', ' ');
            return res;
        }

        public static string GetCompositionObjectsAsValues(Type compositClass)
        {
            string res = "";
            foreach (PropertyInfo navigationProp in GetNavigationProperties(compositClass))
            {
                string navigationObjectName = navigationProp.PropertyType.GetGenericArguments()[0].Name;
                res += navigationObjectName.Substring(0, 1).ToLower() + navigationObjectName.Substring(1) + ", ";
            }

            foreach (PropertyInfo prop in GetPrimitiveProperties(compositClass))
            {
                res += prop.Name.Substring(0, 1).ToLower() + prop.Name.Substring(1) + ", ";
            }
            res = res.TrimEnd(',', ' ');
            return res;
        }
        #endregion Format objects for generation

        

        #region Has... Method
        public static bool HasGetGraphByIdMethod(Type serviceContract)
        {
            return serviceContract.GetInterfaces().Where(interf => (!interf.Name.Contains("ISBase")) && (interf.GetMethods().Where(metho => metho.Name.StartsWith("Get") && metho.Name.EndsWith("GraphById")).Count() > 0)).Count() > 0;
        }

        public static bool HasSearchGraphMethod(Type serviceContract)
        {
            return serviceContract.GetInterfaces().Where(interf => (!interf.Name.Contains("ISBase")) && (interf.GetMethods().Where(metho => metho.Name.StartsWith("Search") && metho.Name.EndsWith("Graph")).Count() > 0)).Count() > 0;
        }

        public static bool HasMethod(Type serviceContract, string methodName)
        {
            MethodInfo method = GetMethod( serviceContract,  methodName);
            return method != null && method != default(MethodInfo);
        }

        public static MethodInfo GetMethod(Type serviceContract, string methodName)
        {
            return serviceContract.GetMethods().Where(m => m.Name == methodName).FirstOrDefault();
        }

        public static string GetParametersWithTypes(MethodInfo method)
        {
            string res = "";
            foreach (ParameterInfo param in method.GetParameters())
            {
                res += FormatParameterType(param.ParameterType) + " " + param.Name + ", ";
            }
            return res.Trim(',', ' ');
        }
        public static string GetParametersWithoutTypes(MethodInfo method)
        {
            string res = "";
            foreach (ParameterInfo param in method.GetParameters())
            {
                res += param.Name + ", ";
            }
            return res.Trim(',', ' ');
        }

        public static string FormatParameterType(Type type)
        {
            string res = type.Name;
            if (type.IsGenericType)
            {
                if (type.GetGenericArguments().Count() > 0)
                {
                    res = type.GetGenericArguments()[0].Name;
                }

                if (type.GetGenericTypeDefinition().UnderlyingSystemType == typeof(System.Nullable<>))
                {
                    res += "?";
                }
                else if (type.GetGenericTypeDefinition().UnderlyingSystemType == typeof(System.Collections.Generic.List<>))
                {
                    res = "System.Collections.Generic.List<" + res + ">";
                }
            }
            return res;
        }


        public static string GetReturnedParameter(MethodInfo method)
        {
            string res = "";
            ParameterInfo param = method.ReturnParameter;
            return FormatParameterType(param.ParameterType);
        }

        #endregion

        #region Get Services contract methods
        public static Type GetServiceContract(PropertyInfo prop)
        {
            return (Type)GetCustomAttributeProperty(prop, "ServiceContract");
        }

        //public static string GetGetByIdMethodName(PropertyInfo property)
        //{
        //    return (string)GetCustomAttributeProperty(property, "GetByIdMethodName");
        //}
        //public static string GetSaveMethodName(PropertyInfo property)
        //{
        //    return (string)GetCustomAttributeProperty(property, "SaveMethodName");
        //}
        //public static string GetDeleteMethodName(PropertyInfo property)
        //{
        //    return (string)GetCustomAttributeProperty(property, "DeleteMethodName");
        //}

        //public static string GetGetByParentID(PropertyInfo property)
        //{
        //    return (string)GetCustomAttributeProperty(property, "GetByParentID");
        //}

        public static string GetGetByParentIDMethodName(PropertyInfo prop)
        {
            return (string)GetCustomAttributeProperty(prop, "GetByParentID", false);
        }

        public static string GetSearchMethodName(PropertyInfo prop)
        {
            return (string)GetCustomAttributeProperty(prop, "Search", false);
        }

        public static bool IsReadOnly(PropertyInfo property)
        {
            if(GetCustomAttributeProperty(property, "ReadOnly") != null )
                return (bool)GetCustomAttributeProperty(property, "ReadOnly");
            return false;
        }
        #endregion



        public static PropertyInfo GetCompositionObjectFromNavigationType(Type descriptor, Type navigationType)
        {
            List<Type> res = new List<Type>();
            foreach (PropertyInfo property in descriptor.GetProperties())
            {
                if (property.PropertyType.Name == "List`1" && property.PropertyType.GetGenericArguments().Count() > 0)
                    if (property.PropertyType.GetGenericArguments()[0] == navigationType)
                        return property;
            }
            throw (new InferAssemblyException("\t\t- Objet de composition non trouvé de : " +
                Environment.NewLine + "\t\t- " + descriptor.Name + " -> \"" + navigationType.Name + "\""));
        }
    }

    public class InferAssemblyException : Exception
    {
        private static string _context
        {
            get
            {
                string context = "Erreur dans ";
                int count = 0;
                foreach(StackFrame frame in new StackTrace().GetFrames())
                {
                    if(count > 0)
                        context += frame.GetMethod().DeclaringType.Name + "." + frame.GetMethod().Name + " :" + Environment.NewLine;
                    count++;
                }
                return context;
            }
        }
        public InferAssemblyException(string message)
            : base(string.Format("{0}  {1}", message , _context))
        {
        }
    }
   
}

